以前架設網站系統,都是for台灣的使用者,因此從未考慮過當使用者來自各個國家時,怎樣按照各地區去處理時區問題。講到這裡,肯定有人會覺得說,使用UTC不就可以解決了嗎?
沒錯,使用UTC是一個非常好的解法,但是說規說,那實際上開發的規範要如何訂定,才能完美達成這需求呢?時區問題在網站的前、後端和資料庫,又要怎樣實作出時區自動化呢?
本文將使用 .net core做為後端為範例講解時間資料的變化。
先不解釋這麼多,我們從source code以及demo來討論。
這裡設計兩支API,分別使用字串以及datetime去做時間處理
public class ExampleController : ControllerBase
{
[HttpGet("{str_date}")]
public IActionResult GetTimeZone(string str_date)
{
CultureInfo provider = CultureInfo.InvariantCulture;
DateTime UTCTime = DateTime.ParseExact(str_date, "yyyyMMddHHmmss", provider).ToUniversalTime();
DateTime LocalTime = DateTime.ParseExact(str_date, "yyyyMMddHHmmss", provider).ToLocalTime();
return Ok(new { str_date, UTCTime, LocalTime});
}
[HttpGet("dt/{demotime}")]
public IActionResult UpdateTimeZone(DateTime demotime)
{
DateTime UTCTime = demotime.ToUniversalTime();
DateTime LocalTime = demotime.ToLocalTime();
return Ok(new {UTCTime, LocalTime});
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Date Time Demo</title>
<style>
.codeblock {
display: block; /* fixes a strange ie margin bug */
font-family: Courier New;
font-size: 10pt;
overflow:auto;
background: #f0f0f0 url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAASwCAYAAAAt7rCDAAAABHNCSVQICAgIfAhkiAAAAQJJREFUeJzt0kEKhDAMBdA4zFmbM+W0upqFOhXrDILwsimFR5pfMrXW5jhZr7PwRlxVX8//jNHrGhExjXzdu9c5IiIz+7iqVmB7Hwp4OMa2nhhwN/PRGEMBh3Zjt6KfpzPztxW9MSAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzB8HS+J9kUTvzEDMwAAAABJRU5ErkJggg==) left top repeat-y;
border: 1px solid #ccc;
padding: 10px 10px 10px 21px;
max-height:1000px;
line-height: 1.2em;
}
.my-block{
padding-left: 10px;
border-style: solid;
border-color: black;
margin-bottom: 30px;
padding-bottom: 20px;
}
</style>
</head>
<body>
<div style="margin-bottom: 30px;">
<div class="my-block">
<p>This block will demo string to DateTime in C#</p>
<label>date with string format : yyyyMMddHHmmss
<input type="text" id="str-date" placeholder="20220101000000">
</label><br><br>
<button onclick="getTimeZone()">Click me to get time zone</button>
</div>
<div class="my-block">
<p>This block will demo how utc and local time work in C#</p>
<label>Select a Date :
<input type="datetime-local" id="demo-date">
</label><br><br>
<button onclick="getTimeZone2()">Click me to change time between utc and local</button>
</div>
</div >
<div id="showdata" class="my-block"></div>
<div class="my-block">
The source code of demo
<pre id="source-code" class="codeblock"></pre>
</div>
<script>
getTimeZone = () =>{
const strdate = document.getElementById("str-date").value
fetch(`timezonetest/api/example/${strdate}`, {
headers: {'content-type': 'application/json'},
method: "GET"
}).then(res => res.json())
.then(data => {
let e = document.getElementById("showdata")
e.innerHTML = `
Input date : ${data.str_date.substring(0, 4)}-${data.str_date.substring(4, 6)}-${data.str_date.substring(6, 8)} ${data.str_date.substring(8, 10)}:${data.str_date.substring(10, 12)}:${data.str_date.substring(12, 14)}<br>
UTC Time in String : ${data.utcTime}<br>
UTC Time in DateTime : ${new Date(data.utcTime)}<br>
Local Time in String : ${data.localTime}<br>
Local Time in DateTime : ${new Date(data.localTime)}`
})
document.getElementById("source-code").innerText = `public IActionResult GetTimeZone(string str_date)
{
CultureInfo provider = CultureInfo.InvariantCulture;
DateTime UTCTime = DateTime.ParseExact(str_date, "yyyyMMddHHmmss", provider).ToUniversalTime();
DateTime LocalTime = DateTime.ParseExact(str_date, "yyyyMMddHHmmss", provider).ToLocalTime();
return Ok(new { str_date, UTCTime, LocalTime});
}`
}
getTimeZone2 = () => {
let demodate = document.getElementById("demo-date").value
fetch(`timezonetest/api/example/dt/${new Date(demodate).toISOString()}`, {
headers: {'content-type': 'application/json'},
method: "GET"
}).then(res => res.json())
.then(data => {
let e = document.getElementById("showdata")
e.innerHTML = `
UTC Time in String : ${data.utcTime}<br>
UTC Time in DateTime : ${new Date(data.utcTime)}<br>
Local Time in String : ${data.localTime}<br>
Local Time in DateTime : ${new Date(data.localTime)}`
})
document.getElementById("source-code").innerText = `public IActionResult UpdateTimeZone(DateTime demotime)
{
DateTime UTCTime = demotime.ToUniversalTime();
DateTime LocalTime = demotime.ToLocalTime();
return Ok(new {UTCTime, LocalTime});
}
`
}
</script>
</body>
</html>
兩支API使用結果都是正常的,送出去的時間跟拿到的時間都是一樣的
如下圖,我送出2022年1月1日0時0分,得到2022年1月1日0時0分
如下圖,我送出2022年1月1日0時0分,得到2022年1月1日0時0分
可以看到第一支API用字串的就出錯了,送出去的時間和得到的時間不一樣
如下圖,依照正常想法來說,我身在日本送出2022年1月1日0時0分,所得到的結果應該如下
但是API的結果卻是
如果改成使用datetime的data type去傳送,由於自帶時區判斷,等於送到後端時會註明為UTC+9,因此只要前端也是透過date(Javascript的日期date type命名為date)的格式去傳接資料,就不用擔心時區問題。
文中並沒有提到時區問題在資料庫怎樣處理,原因是資料庫主要是儲存資料為主,事實上怎樣儲存資料並不影響時區的判斷,因此資料庫並沒有那麼要求要用datetime的data type去儲存資料。
只要後端與資料庫的時間為同一時區即可,就算是國際化的網站,也不必強調一定要使用UTC的時間來記錄。
當今天我們的後端程式always在同一個時區底下執行,那當然不需要考慮太多問題,然而有沒有可能,今天後端server的位置不會一直固定在一個地方呢?如果資料庫儲存的是UTC+8,但是後端會移動到UTC+9、UTC-8等等時區,這種時候當然就要使用UTC時間作為統一的時區標準。
在此有兩種假設
如果你的公司是跨國企業,在這種情況下,顯然使用UTC來記錄時間就會是最萬無一失的情況,讓資料在傳遞時都特別強調為UTC時間,就可以避免掉這容易被忽略的問題。